from AccessControl import ClassSecurityInfo
from AccessControl import getSecurityManager
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.User import UnrestrictedUser
from Acquisition import aq_inner
from App.class_init import InitializeClass
from BTrees.OOBTree import OOBTree
from collective.contentleadimage.config import IMAGE_FIELD_NAME
from collective.contentleadimage.extender import LeadImageExtender
from collective.contentleadimage.extender import LeadimageImageField
from collective.contentleadimage.interfaces import ILeadImageable
from five import grok
from OFS.PropertyManager import PropertyManager
from OFS.SimpleItem import SimpleItem
from ownie.shorturl.events import AddShortURLEvent
from ownie.shorturl.events import IBogusURL
from ownie.shorturl.events import IShortURLUnindexed
from ownie.shorturl.events import provideInterfaces
from ownie.shorturl.interfaces import IBrokenImage
from ownie.shorturl.interfaces import IShortURLTool
from ownie.shorturl.util import *
from Products.Archetypes.atapi import *
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.utils import registerToolInterface
from Products.CMFCore.utils import UniqueObject
from zope.annotation.interfaces import IAnnotations
from zope.app.container.contained import ObjectAddedEvent
from zope.app.container.contained import ObjectMovedEvent
from zope.app.container.contained import ObjectRemovedEvent
from zope.event import notify
from zope.interface import alsoProvides
from zope.interface import implements
from zope.interface import Interface
from zope.interface import noLongerProvides
from zope.interface import providedBy
from zope.lifecycleevent import ObjectModifiedEvent
from OFS.SimpleItem import SimpleItem
import os
import re
import time
import urllib
import urllib2
from Products.CMFCore import permissions
from PIL import Image
import traceback
import ownie.shorturl
import twitter

busy = False
#
Tool_schema = BaseSchema.copy()
invalidHostCharacters = ',/\\<>:;\'"{}[]_+=)(*&^%$#@!)±§~`'


class ShortURLTool(UniqueObject, BaseContent):
    """ This Tool provides objects with the next short ID.
    """
    grok.provides(IShortURLTool)
    id = 'portal_shorturl'
    meta_type = portal_type = 'ShortURLTool'
    _properties = ()
    schema = Tool_schema
    archetype_name = 'CMF Short URL Tool'
    security = ClassSecurityInfo()
    manage_options=( SimpleItem.manage_options
                   )
    security.declareProtected(permissions.ManagePortal, 'seed')
    def seed(self, url, REQUEST=None):
        """
        """
        try:
            self._saved_urls[self._seed] = url
            return self._seed
        finally:
            self._seed = incrCharacter(self._seed)
    
    def visitStats(self):
        """
        """
        return self._visits
        
    def takeMeAway(self, short, REQUEST):
        """
        """
        if short.startswith('/'):
            short = short.replace('/', '')
        try:
            REQUEST.RESPONSE.redirect(self._saved_urls[short])
            if not self._visits.has_key(short):
                self._visits[short] = []
            self._visits[short] += [(time.time(), REQUEST['HTTP_USER_AGENT'], REQUEST['REMOTE_ADDR'])]
        except Exception, e:
            traceback.print_exc()
            postPortalMessage(u'Unable to redirect.', self)
            REQUEST.RESPONSE.redirect('http://my.own.io/whoops')
    
    security.declareProtected(permissions.ManagePortal, 'getLongUrlFromShortUrl')
    def getLongUrlFromShortUrl(self, seed):
        """
        """
        return self._saved_urls[seed]
    
    security.declareProtected(permissions.ManagePortal, 'getPageData')
    def getPageData(self, url):
        """
        """
        try:
            headers = { 'User-Agent' : 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-us) AppleWebKit/531.21.8 (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10' }

            req = urllib2.Request(url, '', headers)
            try:
                return urllib2.urlopen(req).read()
            except:
                return urllib.urlopen(url).read()
        except Exception, e:
            postPortalMessage(u'Unable to retrieve the page that was requested.', self)
            traceback.print_exc()
            return None
            
    security.declareProtected(permissions.ManagePortal, 'attachImageToLink')
    def attachImageToLink(self, link):
        """
        """
        try:
            if not ILeadImageable.providedBy(link):
                return False
            linkId = link.getId()
            url = link.getRemoteUrl()
            try:
                leadImage = link.getField('leadImage')
                os.system('sudo webkit2png --delay=5 --filename=/tmp/%s --fullsize "%s"' % (linkId, url))
                if os.path.exists('/tmp/%s-full.png' % linkId):
                    path = '/tmp/%s-full.png' % linkId
                else:
                    path = os.path.sep.join(ownie.shorturl.__file__.split(os.path.sep)[:-1] + ['static', 'pending.png'])
                im = Image.open(path)
        
                im = im.crop(genBox(im))
                im = im.resize((600, 200), Image.ANTIALIAS)
        
                im.save('/tmp/%s-full2.png' % linkId)
                imageData = open('/tmp/%s-full2.png' % linkId, 'rb').read()
                leadImage.set(link, imageData)
            except Exception, e:
                postPortalMessage(u'I was unable to render a preview image for this URL.', self)
                alsoProvides(link, IBrokenImage)
        finally:
            noLongerProvides(link, IShortURLUnindexed)

    security.declareProtected(permissions.ManagePortal, 'attachMetadataToLink')
    def attachMetadataToLink(self, link, data):
        """
        """
        linkId = link.getId()
        # Extract Title from meta-tag
        try:
            title = extractMeta('title', data)
            print 'link[title-meta]:', linkId, title
            if not title:
                # Extract Title from title-tag
                title = extract('<[\w]*title[\w]*>[^<]*<[\w]*/[\w]*title[\w]*>', data)
                titleStart = extract('<[\w]*title[\w]*>', title)
                titleEnd = extract('<[\w]*/[\w]*title[\w]*>', title)
                title = title.replace(titleStart, '').replace(titleEnd, '')
                print 'link[title-tag]:', linkId, title
            if title:
                print 'tite', title
                link.setTitle(prettyString(title))
        except Exception, e:
            traceback.print_exc()
            #alsoProvides(link, IBrokenImage)
        # Set Description..
        # From meta tag
        try:
            description = extractMeta('description', data)
            print 'link[description-meta]:', linkId, description
            if not description:
                descriptions = re.findall('<[\w]*p[\w]*>[^<]*<[\w]*/p[\w]*>', data, re.IGNORECASE)
                if descriptions:
                    description = '\n'.join([d for d in descriptions if len(d.strip()) > 10])
                    remove = extract('<[^<]*>', description)
                    while remove:
                        description = description.replace(remove, '')
                        remove = extract('<[^<]*>', description)
                print 'link[description-tag]:', linkId, description
            if description:
                link.setDescription(prettyString(description)[:997] + '...')
        except Exception, e:
            traceback.print_exc()
            #alsoProvides(link, IBrokenImage)
        try:
            # Set Keywords..
            # From meta tag
            keywords = extractMeta('keywords', data)
            print 'link[keywords-meta]:', linkId, keywords
            if not keywords:
                descriptions = re.findall('<a.*link-category.*>.*</a[\w]*>', data, re.IGNORECASE)
                if descriptions:
                    description = '\n'.join(descriptions)
                    remove = extract('<+[\w\s"\'=:;/\._\- #,&\?(){}%]*>+', description)
                    while remove:
                        description = description.replace(remove, '')
                        remove = extract('<+[\w\s"\'=:;/\._\- #,&\?(){}%]*>+', description)
                    keywords = ','.join(description.split())
            if keywords:
                link.setSubject([prettyString(keyword.lower().strip()) for keyword in keywords.split(',') if keyword.strip()])
        except Exception, e:
            traceback.print_exc()
            #alsoProvides(link, IBrokenImage)
    
    security.declareProtected(permissions.ManagePortal, 'indexShortUrl')
    def indexShortUrl(self, link):
        """
        """
        url = link.getRemoteUrl()
        print 'Indexing...', url
        data = self.getPageData(url)
        if not data:
            alsoProvides(link, IBogusURL)
            noLongerProvides(link, IShortURLUnindexed)
            link.setTitle("Oops!")
            link.setDescription("A problem occurred. I was unable to render a preview. Please check that this URL is correct.")
            link.reindexObject()
            return

        self.attachImageToLink(link)
        self.attachMetadataToLink(link, data)
        
        link.reindexObject()
    
    def getShortUrlFromLongUrl(self, url):
        """
        """
        return self._inverse_urls[url][0]
        
    def twitThis(self, link):
        """
        """
        try:
            print 'twit_this', self.ifIndexedGetShortUrl(link.getRemoteUrl())
            short = 'http://own.io/' + self._inverse_urls[self.ifIndexedGetShortUrl(link.getRemoteUrl())][0]
            
            if IShortURLUnindexed.providedBy(link):
                return short
            words = []
            keys = ''
            title = link.title_or_id()
            
            for item in link.Subject():
                keyword = "# " + item.split()[0]
                if keyword not in words:
                    if len(short + keyword + keys) <= 50:
                        keys += keyword
                        words += ['#'+item.split()[0]]
            keys = ' '.join(words)
            
            if len(title + keys + short) > 140:
                title = title.split('-')[0]
                title = title.split('|')[0]
            title = title[:140-len(keys + ' ' + short)].strip()
            
            trunk = 0
            twit = twit = ' '.join((title[:-trunk], keys, short))
            while len(twit) > 140:
                trunk += 1
                twit = twit = ' '.join((title[:-trunk], keys, short))
            
            twit = ' '.join((title, keys, short)).replace("&", '%26').replace("#", '%23')
            print len(twit), twit
            return twit
        except Exception, e:
            traceback.print_exc()
            return str(e)
    
    security.declareProtected(permissions.ManagePortal, 'loginAsUnrestrictedUser')
    def loginAsUnrestrictedUser(self):
        """Exemple of use :
            old_user = loginAsUnrestrictedUser()
            # Manager stuff
            loginAsUser(old_user)
        """
        current_user = getSecurityManager().getUser()
        newSecurityManager(None, UnrestrictedUser('manager', '', ['Manager'], []))
        return current_user
        
    def goodUrl(self, url):
        """
        """
        try:
            print 'I got', url
            url = 'http://' + url.strip()
            protocol, url = url.split('://')[-2:]
            if not url.count('.'):
                postPortalMessage(u'Invalid Hostname in URL.', self)
                return False
            host = url.split('/')[0]
            for character in host:
                if character in invalidHostCharacters:
                    postPortalMessage(u'Invalid Hostname in URL.', self)
                    return False
            url = protocol.lower() + '://' + host.lower() + url.replace(host, '')
            if host.lower() in ['own.io', 'is.gd', 'tinyurl.com', 'bit.ly', 'ow.ly', 're.zoink.it', 'goo.gl', '.to']:
                postPortalMessage(u'This URL is already used by a short URL service.', self)
                return False
            return url
        except Exception, e:
            postPortalMessage(u'That was one bad URL. Please try again.', self)
            traceback.print_exc()
            return False
    
    def ifIndexedGetShortUrl(self, url):
        """
        """
        print 'ifIndexedGetShortUrl{%s}' % url
        exists = self._inverse_urls.has_key(url)
        if not exists and (url[-1] == '/'):
            url = url[:-1]
            exists = self._inverse_urls.has_key(url)
        elif not exists and (url[-1] != '/'):
            url = url + '/'
            exists = self._inverse_urls.has_key(url)
        if exists:
            return url
        else:
            return False
            
    def getHost(self, url):
        """
        """
        try:
            protocol, url = url.split('://')[-2:]
        
            return url.split('/')[0]
        except Exception, e:
            traceback.print_exc()
            return False
    
    security.declarePublic('addLink')
    def addLink(self, url, quick=False, api=False, REQUEST=None):
        """
        """
        self.loginAsUnrestrictedUser()
        url = self.goodUrl(url)
        if not url:
            REQUEST.RESPONSE.redirect('http://my.own.io/whoops')
            return
        print 'url', url
        
        portal = getToolByName(self, 'portal_url').getPortalObject()
        catalog = getToolByName(self, 'portal_catalog')
        
        exists = self.ifIndexedGetShortUrl(url)
        print 'exists', exists
        
        if not exists:
            shorturl = getToolByName(self, 'portal_shorturl').seed(url)
            firstLetter = shorturl[0]
            links = portal.links
        
            if not links.hasObject(firstLetter):
                links.invokeFactory(type_name = 'Folder', id=firstLetter)
            container = portal.links[firstLetter]
            container.invokeFactory(type_name = 'Link', id=shorturl)
            print 'adding', shorturl
            link = container[shorturl]
            link.setTitle('Click here to go back to: ' + self.getHost(url))
            link.setRemoteUrl(url)
            provideInterfaces(link, ObjectAddedEvent(link, newParent=container, newName=shorturl))
            request = {'HTTP_USER_AGENT': str(REQUEST['HTTP_USER_AGENT']),
                        'SESSION': str(REQUEST['SESSION']),
                        'HTTP_ACCEPT_LANGUAGE': str(REQUEST['HTTP_ACCEPT_LANGUAGE']),
                        'REMOTE_ADDR': str(REQUEST['REMOTE_ADDR']),
                        'HTTP_COOKIE': str(REQUEST['HTTP_COOKIE']),
                        }
            notify(AddShortURLEvent(link, shorturl, request))
            print "Logged by", REQUEST['REMOTE_ADDR']
            self._inverse_urls[url] = (shorturl, link.UID())
            link.reindexObject()
        else:
            shorturl, uid = self._inverse_urls[exists]
            link = catalog(UID = uid)[0].getObject()
        if api:
            return shorturl
        elif REQUEST:
            postPortalMessage("Successfully added a bookmark. Please wait for the page to refresh.", self)
            REQUEST.RESPONSE.redirect(link.absolute_url())
            #ThreadFindIShortURLUnindexed(self).run()
            #self.findIShortURLUnindexed()
    
    security.declareProtected(permissions.ManagePortal, 'findIShortURLUnindexed')
    def findIShortURLUnindexed(self):
        """
        """
        global busy
        if busy:
            print "findIShortURLUnindexed already busy"
            return
        busy = True
        try:
            print 'checking for new urls',
            catalog = getToolByName(self, 'portal_catalog')
            portal = self.aq_inner.aq_parent#getToolByName(context, 'portal_url')
            results = catalog(object_provides = 'ownie.shorturl.events.IShortURLUnindexed', sort_on='created')[:5]
            print 'Indexing', len(results)
            for brain in results:
                link = brain.getObject()
                try:
                    self.indexShortUrl(link)
                except Exception, e:
                    traceback.print_exc()
            messages = []
            messageCount = len(results)
            if messageCount:
                try:
                    urlLength = 140 / messageCount
                    for brain in results:
                        try:
                            link = brain.getObject()
                            url = self.getShortUrlFromLongUrl(self.ifIndexedGetShortUrl(link.getRemoteUrl()))
                            message = link.title_or_id() + ' http://own.io/' + url
                            message = message[:urlLength-1]
                            messages += [message]
                        except Exception, e:
                            traceback.print_exc()
                    message = '\n'.join(messages)
                    tw = twitter.Api('twitter@own.io', 'own1024')
                    tw.PostUpdate(message)
                except Exception, e:
                    traceback.print_exc()
        finally:
            busy = False
            print "Done indexing"
            
registerType(ShortURLTool, 'ownie.shorturl')
